/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You under the Apache License, Version 2.0
 * (the "License"); you may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.lvyh.lightframe.core.consumer;

import com.lvyh.lightframe.common.constant.RpcConstants;
import com.lvyh.lightframe.common.util.CommonUtils;
import com.lvyh.lightframe.common.util.StringUtil;
import com.lvyh.lightframe.core.cache.RpcReferenceCache;
import com.lvyh.lightframe.core.config.ConsumerConfig;
import com.lvyh.lightframe.core.annotation.Reference;
import com.lvyh.lightframe.core.consumer.proxy.ProxyManager;
import com.lvyh.lightframe.core.consumer.reference.ReferenceModel;
import com.lvyh.lightframe.common.ext.ExtensionLoaderFactory;
import com.lvyh.lightframe.core.invoke.RpcInvokeContext;
import com.lvyh.lightframe.core.serialize.Serializer;
import com.lvyh.lightframe.common.exception.RpcRuntimeException;
import com.lvyh.lightframe.core.util.SpringContextUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.config.InstantiationAwareBeanPostProcessorAdapter;
import org.springframework.util.ReflectionUtils;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;

/**
 * @author lvyh 2021/05/15.
 */
public class ConsumerInvokeManager extends InstantiationAwareBeanPostProcessorAdapter implements InitializingBean, DisposableBean, BeanFactoryAware {
    private Logger logger = LoggerFactory.getLogger(ConsumerInvokeManager.class);

    private BeanFactory beanFactory;

    @Override
    public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
        this.beanFactory = beanFactory;
    }


    @Override
    public boolean postProcessAfterInstantiation(final Object bean, final String beanName) throws BeansException {

        ReflectionUtils.doWithFields(bean.getClass(), new ReflectionUtils.FieldCallback() {
            @Override
            public void doWith(Field field) throws IllegalArgumentException, IllegalAccessException {
                if (field.isAnnotationPresent(Reference.class)) {
                    Class serviceClass = field.getType();
                    if (!serviceClass.isInterface()) {
                        throw new RpcRuntimeException("reference service must be interface.");
                    }

                    ConsumerConfig consumerConfig = SpringContextUtils.getBean(ConsumerConfig.class);
                    String serializerAlias = consumerConfig.getSerializer();
                    Serializer serializer = ExtensionLoaderFactory.getExtensionLoader(Serializer.class).getExtension(serializerAlias);

                    Reference reference = field.getAnnotation(Reference.class);
                    String invokeType = !StringUtils.isEmpty(reference.invokeType()) ? reference.invokeType() : RpcConstants.INVOKER_TYPE_SYNC;
                    long timeout = reference.timeout() > RpcConstants.DEFAULT_INVOKE_TIMEOUT ? reference.timeout() : RpcConstants.DEFAULT_INVOKE_TIMEOUT;
                    ReferenceModel referenceModel = new ReferenceModel.Builder()
                            .setTransport(reference.transport())
                            .setLoadBalance(consumerConfig.getLoadBalance())
                            .setSerializer(serializer)
                            .setInvokeType(invokeType)
                            .setInterfaceName(serviceClass.getName())
                            .setVersion(reference.version())
                            .setAddress(reference.address())
                            .setTimeout(timeout)
                            .setCluster(reference.cluster())
                            .build();


                    //Create client proxy object
                    ProxyManager proxyManager = ProxyManager.getInstance();
                    Object serviceProxy = proxyManager.getProxy(serviceClass, null);

                    field.setAccessible(true);

                    //Set service property as proxy object,for example,OrderInfoService orderInfoService->proxy object
                    field.set(bean, serviceProxy);

                    String serviceUniqueCode = CommonUtils.buildServiceUniqueCode(serviceClass.getName(), reference.version());
                    logger.info("init reference service success, serviceKey: {}, beanName: {}, field: {}", serviceUniqueCode, beanName, field.getName());

                    String serviceKey = CommonUtils.buildServiceUniqueCode(serviceClass.getName(), reference.version());
                    RpcReferenceCache.put(serviceKey, referenceModel);
                }
            }
        });

        return super.postProcessAfterInstantiation(bean, beanName);
    }

    @Override
    public void afterPropertiesSet() throws Exception {
    }

    @Override
    public void destroy() throws Exception {
        RpcInvokeContext rpcInvokeContext = SpringContextUtils.getBean(RpcInvokeContext.class);
        rpcInvokeContext.destroy();
    }


}
